前一章整理了專案架構。這一章要做一件更現實的事:檢查前面由 Assistant 逐步生成出來的系統,有沒有把「信任」放錯地方。
Roblox 是 multiplayer 平台。只要玩家的 client 能觸發 server action,就要假設有人可以用非預期方式呼叫它。UI button、Tool、InputAction、ProximityPrompt、ClickDetector、RemoteEvent 都不只是正常玩家操作入口,也可能是攻擊入口。
本章不會把遊戲變成完整反外掛產品。目標比較務實:讓目前專案的主要 gameplay remotes 有基本 server-side validation,並建立一份你之後每次請 Assistant 生成新功能都要使用的安全審查流程。
這裡的 PurchaseUpgrade 仍是 Gold 商店。真實平台交易會在第 33–34 章加入額外邊界:client 不能宣稱付款成功,Developer Product 必須由唯一的 ProcessReceipt 入口交付,同一筆 receipt 也不能重複發放。
完成本章後,你會得到:
RequestAbility 的基本安全檢查。PurchaseUpgrade 的基本安全檢查。RequestGuideHint 的基本安全檢查。SaveService 的資料邊界檢查原則。這一章的核心句是:
Client can request. Server decides.
client 可以請求使用能力、購買升級、顯示提示、播放特效。client 不能決定玩家是否真的成功、要扣多少 Gold、cooldown 是多少、獎勵是多少、要保存什麼資料。

圖 20-1 Client 只傳送購買意圖,server 必須重新驗證商品、價格、餘額與結果,AI 生成的 Remote handler 也不例外。

圖 20-2 Server Authority 相關物件仍需放在正確的 DataModel 位置;位置只是基礎,真正的安全邊界仍是由 server 驗證並決定結果。
本章請先把以下 context 給 Assistant:
【Ch20 Context|稽核 Client/Server 安全】
我們正在 Roblox Studio 中製作「AI Adventure Island」。
目前架構:
- Server gameplay scripts 位於 ServerScriptService.Services。
- 共用 config modules 位於 ReplicatedStorage.Config。
- RemoteEvents 位於 ReplicatedStorage.Remotes。
- HUD LocalScripts 位於 StarterGui.AdventureHUD.Controllers。
現有系統:
- QuestService 處理 Guide 任務與獎勵。
- AbilityService 處理 WindDash。
- ShopService 處理升級購買。
- SaveService 保存 Gold、GuideQuestState 與 WindDash 升級 attributes。
安全目標:
- Client 只能傳送玩家意圖。
- Server 必須驗證 type、id、permission、context、cooldown、currency 與 rate limits。
- 不要信任 client 提供的 price、reward、speed、duration、cooldown、level、target player 或 save data。
目前先不要修改任何內容。
請先針對現有 remotes 與 services 產出一份安全稽核表。
OnServerEvent 收到的第一個參數是觸發的 Player,這個 player 不應由 client 自己傳入。目前專案已經能玩:
WindDash。但是這些功能是逐章請 Assistant 長出來的。每章都能跑,不代表整體安全。
請看幾個常見風險。
正常 UI 可能這樣呼叫:
PurchaseUpgrade:FireServer("DashCooldown1")
但不該設計成:
PurchaseUpgrade:FireServer("DashCooldown1", 0, 99)
如果第二、三個參數代表 price 和 level,而 server 直接相信它們,玩家就可能用 0 Gold 購買高級升級。
正常能力 request 應該只送:
RequestAbility:FireServer("WindDash")
不該送:
RequestAbility:FireServer("WindDash", 0.1, 100)
cooldown、duration、speed 都應該由 server 從 AbilityConfig 與玩家升級狀態計算。
危險寫法:
CastEffect.OnServerEvent:Connect(function(player, payload)
CastEffect:FireAllClients(payload)
end)
這段看起來只是轉發特效,但如果 payload 沒有驗證,玩家可以送出錯誤型別、極大 table、NaN 位置、過高頻率的特效要求,讓其他玩家的 client 受到影響。
server 不能只是轉接器。server 必須是 gatekeeper。
Assistant 有時候會為了讓 client/server 都能 require,把大量邏輯放在 ReplicatedStorage 的 ModuleScript 裡。這不一定錯,config 或純共用函式可以放那裡;但 server-only 規則、存檔邏輯、敏感檢查、商店交易邏輯不應該放進會 replicate 給 client 的地方。
本章要把這些風險變成一套固定檢查。
先不要叫 Assistant 直接修改。安全工作第一步是盤點。
【Ch20 主任務|稽核 Client/Server 安全】
稽核這個 Roblox Studio 專案的安全性。
目前系統:
- RequestGuideHint RemoteEvent
- RequestAbility RemoteEvent
- PurchaseUpgrade RemoteEvent
- QuestService
- AbilityService
- ShopService
- SaveService
目前先不要修改 scripts。
建立一份包含以下欄位的安全稽核表:
1. Remote 或 service
2. 收到的 client input
3. Server 目前信任了哪些資料
4. 必要的 type/structure validation
5. 必要的 context/permission validation
6. 必要的 value validation
7. 必要的 rate limit 或 cooldown
8. 哪些項目必須由 server 保持權威
9. 修正後的回歸測試
規則:
- Client 只能傳送意圖。
- 不要信任 client 提供的 price、reward、speed、duration、cooldown、level、target player 或 save data。
- ServerScriptService.Services 是權威層。
- ReplicatedStorage.Config 只放共用設定,不放 secret logic。
- ReplicatedStorage.Remotes 只作為通訊層。
這個 Prompt 有兩個關鍵。
第一,它要求 Assistant 先做表格,不直接修改。安全修改如果一次全改,很容易把原本能跑的功能打壞。
第二,它明確列出「不能信任的欄位」。Assistant 生成 gameplay code 時常會順手讓 client 傳更多資訊,因為那樣寫起來很直覺;但在 multiplayer 安全模型裡,client 傳越多權威資料,風險越高。
你可能會得到類似下面的稽核表。
Remote: RequestAbility
Client input:
- abilityId
Risk:
- client could spam ability requests
- client could request an ability it does not own
- client could send a non-string or unknown ability id
- if current code accepts speed/cooldown/duration from client, that is unsafe
Server validation needed:
- typeof(abilityId) == "string"
- ability id exists in AbilityConfig
- player has character, humanoid exists, humanoid health > 0
- player owns WindCharm or has matching ability attribute
- cooldown is checked on server
- rate limit requests per player
Server authority:
- speed
- duration
- cooldown
- upgraded ability stats
- actual Humanoid.WalkSpeed change
商店可能像這樣:
Remote: PurchaseUpgrade
Client input:
- upgradeId
Risk:
- client could send fake price
- client could send unknown upgrade id
- client could buy repeatedly
- client could spam purchases
Server validation needed:
- typeof(upgradeId) == "string"
- upgrade id exists in ShopConfig
- price is read from server-side require of ShopConfig
- player Gold is read from server state
- duplicate purchase is rejected
- purchase and state update happen in one server flow
Server authority:
- Gold
- upgrade ownership
- upgrade effect
- save-bound player attributes
任務提示可能像這樣:
Remote: RequestGuideHint
Client input:
- maybe no payload, or hint id
Risk:
- client could request reward directly
- client could trigger hint from anywhere
- client could spam guide interaction
Server validation needed:
- player character exists
- player is near Guide NPC or allowed interaction zone
- quest state allows hint
- rate limit hint requests
Server authority:
- quest state
- reward
- progression state
這張表就是本章的工作地圖。接下來才開始修改。
在 Roblox 裡,最重要的分界是:
LocalScript / player input / UI / client visuals
-> RemoteEvent
-> Server Script / Services / authoritative state
client 端適合做:
server 端必須做:
RemoteEvent 位於中間,它只是通道。
RemoteEvent does not mean trusted event.
第一層檢查是型別與結構。
如果 RequestAbility 只接受 abilityId,server 應該先檢查:
if typeof(abilityId) ~= "string" then
return
end
如果 PurchaseUpgrade 只接受 upgradeId:
if typeof(upgradeId) ~= "string" then
return
end
這看起來簡單,但很重要。不要假設 UI button 只會送正確 string。攻擊者不需要按你的 button;他可以直接呼叫 RemoteEvent 並傳入任意值。
也不要接受不必要的 table。
不好的設計:
PurchaseUpgrade:FireServer({
Id = "DashSpeed1",
Price = 0,
Level = 99,
})
比較好的設計:
PurchaseUpgrade:FireServer("DashSpeed1")
payload 越小,驗證越清楚。
型別正確不代表值合理。
typeof(amount) == "number" 不能保證安全,因為 NaN 和 Inf 也可能讓比較邏輯失效。只要某個 RemoteEvent 會接受 number,就應該考慮:
local function isFiniteNumber(value)
if typeof(value) ~= "number" then
return false
end
if value ~= value then
return false
end
if math.abs(value) == math.huge then
return false
end
return true
end
本書目前的主要 gameplay remotes 盡量不讓 client 傳 number。這是更好的設計:不要驗證不需要存在的資料。
例如能力不讓 client 傳 speed:
client sends: "WindDash"
server reads: AbilityConfig.WindDash.BaseSpeed
server applies: upgrade-adjusted speed
商店不讓 client 傳 price:
client sends: "DashCooldown1"
server reads: ShopConfig.DashCooldown1.Cost
server checks: player leaderstats.Gold.Value
第三層檢查是情境與權限。
玩家送出 RequestAbility("WindDash") 時,server 要問:
WindCharm 或對應 ability ownership 嗎?玩家送出 PurchaseUpgrade("DashSpeed1") 時,server 要問:
玩家送出 RequestGuideHint 時,server 要問:
這些檢查不是 UI 的責任。UI 可以灰掉 button,但 server 仍要檢查。
cooldown 是玩法規則,rate limit 是保護 server 的工程規則。兩者都需要。
WindDash 的 cooldown 可能是 6 秒;但如果有人每秒送 200 次 RequestAbility,即使每次都被 cooldown 拒絕,server 也仍然要處理大量 request。因此需要更前面的 rate limit。
可以請 Assistant 生成一個簡單 module,放在 ServerScriptService.Services 或 ServerScriptService.Modules:
【Ch20 實作|建立 Server RateLimiter】
建立一個小型、僅供 server 使用的 RateLimiter ModuleScript。
位置:
- ServerScriptService.Services.Security.RateLimiter
需求:
- 為每一組 user id 與 action name 建立 token bucket。
- allow(player, actionName, capacity, windowSeconds) 回傳 true/false。
- 玩家離開時清理資料。
- 不要將這個 module 放在 ReplicatedStorage。
- 實作應保持簡單,讓初學者也能讀懂。
可能產生的簡化介面:
local RateLimiter = {}
local buckets = {}
local function getBucket(userId, actionName)
buckets[userId] = buckets[userId] or {}
buckets[userId][actionName] = buckets[userId][actionName] or {
Count = 0,
WindowStart = os.clock(),
}
return buckets[userId][actionName]
end
function RateLimiter.Allow(player, actionName, maxCount, windowSeconds)
local bucket = getBucket(player.UserId, actionName)
local now = os.clock()
if now - bucket.WindowStart >= windowSeconds then
bucket.Count = 0
bucket.WindowStart = now
end
if bucket.Count >= maxCount then
return false
end
bucket.Count += 1
return true
end
function RateLimiter.ClearPlayer(player)
buckets[player.UserId] = nil
end
return RateLimiter
這不是最完整的 token bucket,但對目前專案足夠,而且容易理解。等專案變大,再換成更精細的實作。
本書的主軸是使用 Assistant,但不是盲信 Assistant。
你要把 Assistant 當作一位很快的初階開發協作者:它能快速產生可跑的雛形,但你仍然要做 code review。
每次看到 Assistant 生成 RemoteEvent server handler,都問:
FireAllClients() relay 未驗證 payload?ReplicatedStorage?這些問題就是你的 AI code review checklist。
現在請 Assistant 分段修改。先從 RequestAbility 開始。
【Ch20 修正 1|保護 RequestAbility】
更新 AbilityService 對 RequestAbility 的安全處理。
範圍:
- 只編輯 AbilityService,以及必要的小型 server-only validation/rate-limit helper。
- 不要更改 UI layout。
- 不要更改玩法數值。
- 不要更改 RemoteEvent 名稱。
規則:
- Client 只能傳送 abilityId。
- 如果 abilityId 不是 string,拒絕 request。
- 如果 abilityId 不在 ReplicatedStorage.Config.AbilityConfig 中,拒絕 request。
- 如果玩家沒有 character、沒有 Humanoid,或 Humanoid.Health <= 0,拒絕 request。
- 如果玩家未擁有 WindDash/WindCharm,拒絕 request。
- Speed、duration 與 cooldown 由 server 計算。
- Client 不能提供 speed、duration、cooldown、level 或 target player。
- 加入 server-side cooldown 與 rate limit。
- 適合時,透過 AbilityUpdate 將簡短的失敗原因傳給同一位玩家。
編輯後,列出回歸測試。
接著處理商店。
【Ch20 修正 2|保護 PurchaseUpgrade】
更新 ShopService 對 PurchaseUpgrade 的安全處理。
範圍:
- 只編輯 ShopService;如有需要,可編輯共用的 server-only helper modules。
- 不要更改 UI layout。
- 不要更改升級價格或效果。
- 不要更改 DataStore 名稱或 save schema。
規則:
- Client 只能傳送 upgradeId。
- 如果 upgradeId 不是 string,拒絕 request。
- 如果 upgradeId 不在 ReplicatedStorage.Config.ShopConfig 中,拒絕 request。
- Server 從 ShopConfig 讀取 price 與 effect。
- Client 不能提供 price、effect、level 或 target player。
- Server 驗證 leaderstats.Gold。
- Server 拒絕重複購買一次性升級。
- 只有在扣除 Gold 後,server 才能套用升級 attributes。
- 為購買 requests 加入 rate limit。
- 透過 ShopUpdate 將成功/失敗結果傳給同一位玩家。
編輯後,列出回歸測試。
最後處理 Guide hint。
【Ch20 修正 3|保護 RequestGuideHint】
更新 QuestService 或 GuideHintService 對 RequestGuideHint 的安全處理。
範圍:
- 只編輯 Guide hint request flow。
- 不要更改任務獎勵或任務文字。
規則:
- 最好不要有 client payload。如果已有 payload,驗證其 type 與預期值。
- 如果玩家沒有 character 或 HumanoidRootPart,拒絕 request。
- 如果 Guide NPC 或 interaction part 不存在,拒絕 request。
- 如果玩家與互動位置的距離超過允許範圍,拒絕 request。
- 如果 quest state 不允許取得提示,拒絕 request。
- Client 不能要求指定 reward amount 或直接完成任務。
- 為 hint requests 加入 rate limit。
- GuideHintUpdate 只能傳給提出 request 的玩家。
編輯後,列出回歸測試。
這三段 Prompt 的設計原則是:一次只修一條管線。這樣出錯時比較容易定位。
安全章的測試不能只測正常玩家流程。至少要測四種狀況。
正常玩家操作應該仍然可用:
1. 進入 Play mode。
2. 收集水晶。
3. 接近 Guide NPC,取得提示。
4. 使用 WindDash。
5. 確認 cooldown 期間不能再用。
6. 取得 Gold。
7. 打開商店購買升級。
8. Gold 扣除,升級 attribute 更新。
9. 離開與重進後資料仍正確。
如果正常流程壞了,先修正常流程。安全修正不能以破壞遊戲為代價。
在 Studio 測試時,可以臨時建立一段測試 LocalScript 或 Command Bar 測試,送出錯誤資料:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remotes = ReplicatedStorage:WaitForChild("Remotes")
remotes.RequestAbility:FireServer(123)
remotes.PurchaseUpgrade:FireServer({})
server 不應該報紅色錯誤,也不應該給獎勵、扣款或套用能力。
測完後刪掉測試 script,不要留在正式專案裡。
如果某個 remote 仍然接受 number,測 NaN / Inf:
local badNumber = 0 / 0
local hugeNumber = math.huge
本章主線的做法是盡量不讓 client 傳 number。若你的專案後續需要 client 傳座標、方向、數量,就必須加入 finite number 檢查與範圍檢查。
建立臨時測試:
for i = 1, 100 do
remotes.RequestAbility:FireServer("WindDash")
end
期待結果:
用 Studio 的 server/client 視角確認:
完成本章後,專案應該有一份固定安全表。可以放在章節筆記,也可以放進專案文件。
Remote: RequestAbility
Client input:
- abilityId: string
Server checks:
- abilityId type
- ability id exists
- player character alive
- player owns ability
- cooldown
- rate limit
Server-owned values:
- speed
- duration
- cooldown
- upgrade effect
Regression:
- valid WindDash works
- cooldown rejects second request
- invalid ability id rejected
- non-string ability id rejected
- spam does not break server
Remote: PurchaseUpgrade
Client input:
- upgradeId: string
Server checks:
- upgradeId type
- upgrade id exists
- Gold exists and is enough
- not already purchased
- rate limit
Server-owned values:
- price
- effect
- Gold deduction
- upgrade attributes
Regression:
- valid purchase succeeds
- not enough Gold rejected
- duplicate purchase rejected
- invalid upgrade id rejected
- client price is ignored because it is never accepted
Remote: RequestGuideHint
Client input:
- none preferred
Server checks:
- character exists
- HumanoidRootPart exists
- player near Guide
- quest state allows hint
- rate limit
Server-owned values:
- quest state
- reward
- hint availability
Regression:
- near Guide gets hint
- far from Guide rejected
- wrong quest state rejected
- spam does not break server
SaveService 的重點是:不要讓 client 決定存什麼。
不需要也不應該有這種 remote:
SavePlayerData:FireServer(playerDataTable)
保存時,server 應該從自己的 state 組資料:
Gold -> leaderstats.Gold.Value
GuideQuestState -> player attribute
WindDashCooldownLevel -> player attribute
WindDashSpeedLevel -> player attribute
然後再轉成第 18 章定義的 schema。
如果未來玩家可以輸入名字、留言、地圖標籤或自訂內容,才需要處理 UTF-8、長度限制與內容審核。本章目前不加入 user-generated text。
本書第七部會處理 Suno、ElevenLabs、圖片生成、3D 生成等外部 AI 工具。那些章節還沒開始,但安全觀念要先放進這裡。
外部素材進 Roblox 前要問:
這一章先把安全習慣建立起來。第七部會把它擴充成 asset register。
Assistant 生成的程式不一定有惡意,但可能有錯誤假設。外部 AI 生成的圖片、音樂、音效、3D 模型不一定有惡意,但可能有授權、內容、格式、效能或審核問題。
所以本書的 AI 工作流不是:
prompt -> accept -> publish
而是:
prompt -> generate -> inspect -> constrain -> test -> document -> publish
這個流程會在後面每一章反覆出現。
如果 Assistant 修改後仍然相信 client 傳入的 price,使用:
【Ch20 修正 4|移除 Client 價格資料】
目前的 ShopService 仍接受 client 傳入的 price 或 effect data。
請重構成 client 只傳送 upgradeId。
Server 必須從 ShopConfig 讀取 price、effect、max level 與 display name。
拒絕任何由 client 額外提供的 transaction data。
不要更改現有的升級價格或效果。
如果 Assistant 把安全 helper 放進 ReplicatedStorage,使用:
【Ch20 修正 5|移動 Server 安全模組】
將 server-only security helpers 移出 ReplicatedStorage。
Security validation 與 rate limiting modules 應位於 ServerScriptService 下,
因為它們是 server-only logic。
ReplicatedStorage 只保留 shared config 與 RemoteEvents。
請同步更新 require paths。
如果 Assistant 只做 type validation,沒有 context validation,使用:
【Ch20 修正 6|補上情境與權限驗證】
加入 context 與 permission validation。
針對 RequestAbility:
- 檢查 character 是否存在。
- 檢查 Humanoid 是否存在。
- 檢查 Humanoid.Health > 0。
- 檢查玩家是否擁有 WindDash。
- 檢查 server cooldown。
針對 PurchaseUpgrade:
- 檢查 leaderstats.Gold 是否存在。
- 檢查 Gold 是否足夠。
- 檢查該升級是否尚未購買。
不要依賴 UI state。
如果 Assistant 用 FireAllClients() relay client payload,使用:
【Ch20 修正 7|停止轉送 Client Payload】
Server 目前會將 client payloads 轉送給所有 clients。
請改成 server 先驗證 request。
只能 broadcast 經過清理、由 server 建立的資料。
不要將任意 client tables 或 instances 傳給 FireAllClients。
本章沒有新增華麗功能,但它讓遊戲從「能跑」往「比較安全」前進。
你現在應該記住:
下一章會處理效能與大型場景。安全讓玩家不容易破壞規則;效能則讓遊戲在更多裝置上穩定運作。
第 34 章會把本章的 server authority 原則套到真實交易;第 36–37 章則會確保 analytics 與 funnel 事件不能被 client 偽造成成功購買或商品交付。
嗨!我是 Wolke,曾任 Google Developer Expert(GDE,2019–2023) 與 LINE API Expert。
我熱衷於研究 AI Agent、n8n 自動化工作流與全端開發架構,致力於將 AI 技術轉化為真正能落地的生產力工具。
如果你喜歡這篇文章,歡迎透過以下方式與我交流:
📚 技術著作
《實用的 Gemini API 開發點子書》:帶你運用 Gemini App、Google AI Studio、Gemini CLI 與 Antigravity IDE,打造 AI Agent 與實用產品。
📝 技術部落格
歡迎追蹤我的 Medium,我會持續分享 Agentic Automation、架構設計與實際開發的踩坑心得。
🎤 技術講座與合作
我持續受邀至技術社群及研討會,分享 AI Agent、自動化工作流、DevOps 與全端開發實戰。
我曾於 DevOpsDays Taipei 2026 主講「不再只是寫腳本!讓 AI 代理人成為你的 SRE 最佳夥伴」工作坊。
如果你的企業、社群或學校正在尋找相關主題講者,歡迎私訊聯繫,洽談講座與工作坊合作!
🎮 我的 Roblox 遊戲
🎁 免費贈送 OpenAI 或 Claude AI 額度
為了鼓勵大家實際動手打造自己的 Roblox 體驗,我每個月會開放:
參加方式:
確認完成後,我會邀請你加入並設定 50 點額度。名額有限,歡迎把握機會!